/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

bool SqlEngine::checkConditions(const vector<SelCond>& cond, int key, string val)
{
	for(int i = 0; i < cond.size(); i++)
	{
		int diff;
		switch(cond[i].attr)
		{
			case 1:
				diff = key - atoi(cond[i].value);
				break;
			case 2:
				diff = strcmp(val.c_str(), cond[i].value);
				break;
		}

		switch(cond[i].comp) 
		{
			case SelCond::EQ:
				if (diff != 0) 
					return false;
				break;
		      	case SelCond::NE:
				if (diff == 0) 
					return false;
				break;
		     	case SelCond::GT:
				if (diff <= 0) 
					return false;
				break;
			case SelCond::LT:
				if (diff >= 0) 
					return false;
				break;
		      	case SelCond::GE:
				if (diff < 0) 
					return false;
				break;
		      	case SelCond::LE:
				if (diff > 0) 
					return false;
				break;
		}
	}
	return true;
}

RC SqlEngine::selectWithIndex(RecordFile rf, const string& table, int attr, const vector<SelCond>& cond)
{
	int ret = 0;
	
	int maxKey;
	int minKey;
	int eqKey;

	bool hasMin = false;
	bool hasMax = false;
	bool hasEQ = false;
	bool hasNonKeyCond = false;
	
	BTreeIndex index;

	int key;
	string value;
	RecordId rid;

	int count = 0;

	ret = index.open(table + ".idx", 'r');
	
	if(ret != 0)
		return selectWithoutIndex(rf, attr, cond);

	for(int i = 0; i < cond.size(); i++)
	{
		if(cond[i].attr != 1)
		{
			hasNonKeyCond = true;
		} else {
			int val = atoi(cond[i].value);
			switch(cond[i].comp)
			{
				case SelCond::EQ:
					if(!hasEQ)
					{
						hasEQ = true;
						eqKey = val;
					} else {
						if(eqKey != val)
							goto countAndExit;
					}
					break;
				case SelCond::LT:
					if(!hasMax || val - 1 < maxKey)
					{
						hasMax = true;
						maxKey = val - 1;
					}
					
					break;
				case SelCond::GT:
					if(!hasMin || val + 1 > minKey)
					{
						hasMin = true;
						minKey = val + 1;
					}

					break;
				case SelCond::LE:
					if(!hasMax || val < maxKey)
					{
						hasMax = true;
						maxKey = val;
					}

					break;
				case SelCond::GE:
					if(!hasMin || val > minKey)
					{
						hasMin = true;
						minKey = val;
					}

					break;
				default:
					break;
			}
		}
	}

	if(!hasEQ && !hasMin && !hasMax)	//has no key conditions
		if(hasNonKeyCond || attr != 1)	//if the value field needs to be examined
			return selectWithoutIndex(rf, attr, cond);

	IndexCursor cursor;

	if(hasEQ)
	{
		ret = index.locate(eqKey, cursor);
		if(ret == RC_END_OF_TREE)
			goto countAndExit;

		if(ret != 0)
			goto exit;

		ret = index.readForward(cursor, key, rid);
		if(ret != 0)
			goto exit;

		if(key != eqKey)
			goto countAndExit;

		if(!hasNonKeyCond)
		{
			if(!checkConditions(cond, key, ""))
      				goto countAndExit;

			switch(attr)
			{
				case 1:
					fprintf(stdout, "%d\n", key);
					break;
				case 2:
					ret = rf.read(rid, key, value);
					if(ret != 0)
						goto exit;

					fprintf(stdout, "%s\n", value.c_str());
					break;
				case 3:
					ret = rf.read(rid, key, value);
					if(ret != 0)
						goto exit;

					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
				case 4:
					fprintf(stdout, "1\n");
					break;
			}
		} else {
			ret = rf.read(rid, key, value);
			if(ret != 0)
				goto exit;

			if(!checkConditions(cond, key, value))
				goto countAndExit;

			switch(attr)
			{
				case 1:
					fprintf(stdout, "%d\n", key);
					break;
				case 2:
					fprintf(stdout, "%s\n", value.c_str());
					break;
				case 3:
					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
				case 4:
					fprintf(stdout, "1\n");
					break;
			}
		}

		goto exit;
	}

	//No EQ condition
	if(!hasMin)
		minKey = -2147483647; //min value for an int
	
	ret = index.locate(minKey, cursor);
	if(ret != 0)
		goto exit;
	
	ret = index.readForward(cursor, key, rid);
	if(ret == -1)
		goto countAndExit;

	if(ret != 0)
		goto exit;

	while(!hasMax || key <= maxKey)
	{
		if(!hasNonKeyCond)
		{
			if(checkConditions(cond, key, ""))
			{
				count++;
				switch(attr)
				{
					case 1:
						fprintf(stdout, "%d\n", key);
						break;
					case 2:
						ret = rf.read(rid, key, value);
						if(ret != 0)
							goto exit;

						fprintf(stdout, "%s\n", value.c_str());
						break;
					case 3:
						ret = rf.read(rid, key, value);
						if(ret != 0)
							goto exit;


						fprintf(stdout, "%d '%s'\n", key, value.c_str());
						break;
				}	
			}	
		} else {
			ret = rf.read(rid, key, value);

			if(ret != 0)
				goto exit;

			if(checkConditions(cond, key, value))
			{
				count++;
				switch(attr)
				{
					case 1:
						fprintf(stdout, "%d\n", key);
						break;
					case 2:
						fprintf(stdout, "%s\n", value.c_str());
						break;
					case 3:
						fprintf(stdout, "%d '%s'\n", key, value.c_str());
						break;
				}
			}
		}

		if(cursor.pid == -1)
			break;

		ret = index.readForward(cursor, key, rid);
		if(ret == -1)
			break;
	
		if(ret != 0)
			goto exit;
	}

	countAndExit:
	if(attr == 4)
		fprintf(stdout, "%d\n", count);

	exit:
	index.close();

	return ret;
}

RC SqlEngine::selectWithoutIndex(RecordFile rf, int attr, const vector<SelCond>& cond)
{
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
	diff = key - atoi(cond[i].value);
	break;
      case 2:
	diff = strcmp(value.c_str(), cond[i].value);
	break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
	if (diff != 0) goto next_tuple;
	break;
      case SelCond::NE:
	if (diff == 0) goto next_tuple;
	break;
      case SelCond::GT:
	if (diff <= 0) goto next_tuple;
	break;
      case SelCond::LT:
	if (diff >= 0) goto next_tuple;
	break;
      case SelCond::GE:
	if (diff < 0) goto next_tuple;
	break;
      case SelCond::LE:
	if (diff > 0) goto next_tuple;
	break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  exit_select:
  return rc;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RC     rc;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  rc = selectWithIndex(rf, table, attr, cond);
  if(rc != 0)
  {
    fprintf(stderr, "Error: error %d scanning tuples for query\n", rc);
    return rc;
  }

  // close the table file and return
  rf.close();
  return 0;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  int ret;
  BTreeIndex tblIndex;

  if(index)
    tblIndex.open(table + ".idx", 'w');

  fstream infile(loadfile.c_str(), fstream::in);
  if(infile.is_open())
  {
    RecordFile newRec;
    ret = newRec.open(table + ".tbl", 'w');
    if(ret != 0)
      return ret;

    string line;
    int key;
    string value;
    RecordId recID;

    while(infile.good())
    {
          getline(infile, line);
          ret = parseLoadLine(line, key, value);
          if(ret != 0)
	    continue;

          ret = newRec.append(key, value, recID);
          if(ret != 0)
    	    return ret;

          if(index)
          {
	    ret = tblIndex.insert(key, recID);
	    if(ret != 0)
	      return ret;
          }
    } 
    
    newRec.close();
    infile.close();

    IndexCursor cursor;
    tblIndex.locate(251, cursor);
    if(index)
      tblIndex.close();

    return 0;
  } 

  return RC_FILE_OPEN_FAILED;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
